//
//  UMPListener.m
//  UMPDemo
//
//  Created by Jonathan Diehl on 13.01.09.
//  Copyright 2009 Media Computing Group. All rights reserved.
//

#import <ftdi.h>
#import "UMPListener.h"
#import "UMPTouch.h"


// private methods
@interface UMPListener ()
- (BOOL)initFTDI:(NSError **)error;
- (BOOL)deinitFTDI:(NSError **)error;
@end


@implementation UMPListener

@synthesize delegate, umpc;


# pragma mark init & cleanup

// designated init
- (id)init;
{
	self = [super init];
	if (self != nil) {
		NSURL *propertiesURL = [[NSBundle mainBundle] URLForResource:@"ump" withExtension:@"plist"];
		properties = [[NSDictionary alloc] initWithContentsOfURL:propertiesURL];
	}
	return self;
}

// init with delegate
- (id)initWithDelegate:(NSObject<UMPListenerDelegate> *)aDelegate startImmediately:(BOOL)start error:(NSError **)error;
{
	self = [self init];
	if (self != nil) {
		self.delegate = aDelegate;
		if(start) [self start:error];
	}
	return self;
}

// cleanup
- (void)dealloc
{
	[self stop:nil];
	[properties release];
	[touches release];
	[super dealloc];
}


# pragma mark accessors

// assign delegate
- (void)setDelegate:(NSObject <UMPListenerDelegate> *)aDelegate;
{
	delegate = aDelegate;
	
	// determine what optional delegate methods are implemented
	delegateWantsUpdates = [delegate respondsToSelector:@selector(umpListenerDidUpdate:)];
}


# pragma mark device control

// start listening on given device
- (BOOL)start:(NSError **)error;
{
	// make sure we are not currently listening
	if(![self stop:error]) return NO;
	
	// init ftdi interface
	if(![self initFTDI:error]) return NO;
	
	// start thread
	listening = YES;
	listeningThread = [[NSThread alloc] initWithTarget:self selector:@selector(listeningThreadMain) object:nil];
	[listeningThread start];

	return YES;
}

// stop listening on opened port
- (BOOL)stop:(NSError **)error;
{
	// nothing to do if not listening
	if(!listeningThread) return YES;
	
	// cancel the listener thread
	listening = NO;
	[listeningThread cancel];
	[listeningThread release];
	listeningThread = nil;
	
	// deinit ftdi
	return [self deinitFTDI:error];
}


# pragma mark listening thread

// main
- (void)listeningThreadMain;
{
	NSAutoreleasePool *pool;
	
	int bufferSize = [[properties valueForKey:@"bufferSize"] intValue];
	
    unsigned char buf[bufferSize];
	int numBytes;
	ump_touch_list *umpTouches, *umpTouchesIterator;
	int numTouches;
	int f;
	
	NSMutableArray *mutableTouches = [[NSMutableArray alloc] initWithCapacity:10];
	UMPTouch *touch, *prevTouch;
	
	// run loop
	while(listening) {
		pool = [[NSAutoreleasePool alloc] init];
		
		// read serial data from device
		numBytes = ftdi_read_data(&ftdic, buf, sizeof(buf));
		if(numBytes < 0) {
			NSString *errorMessage = [NSString stringWithFormat:@"unable to read serial data: %s", ftdi_get_error_string(&ftdic)];
			NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey];
			NSError *error = [NSError errorWithDomain:@"UMP" code:numBytes userInfo:errorInfo];
			[self performSelectorOnMainThread:@selector(didFailWithError:) withObject:error waitUntilDone:YES];
			break;
		}
		
		// we have read something
		if(numBytes > 0) {
			
			// interpret ump data
			f = ump_read(&umpc, buf, numBytes);
			if(f < 0) {
				NSLog(@"invalid serial data (ump array size may be wrong)");
			}
			
			// read touch data
			numTouches = ump_read_touches(&umpc, &umpTouches, umpc.length / 4);
			
			// clear mutable touches array
			[mutableTouches removeAllObjects];
			
			// convert to UMPTouches
			for(umpTouchesIterator = umpTouches; umpTouchesIterator != NULL; umpTouchesIterator = umpTouchesIterator->next ) {

				// update previous touch (if present)
				if(umpTouchesIterator->prev_touch.pressure > 0.0)
				{
					for(prevTouch in touches)
					{
						if(prevTouch.x == umpTouchesIterator->prev_touch.x && 
						   prevTouch.y == umpTouchesIterator->prev_touch.y)
						{
							touch = [prevTouch updateWithPoint:NSMakePoint(umpTouchesIterator->touch.x, umpTouchesIterator->touch.y)
											  pressure:umpTouchesIterator->touch.pressure];
							[mutableTouches addObject:touch];
							break;
						}
					}
					
				// create new touch (if no previous present)
				} else {
					touch = [[UMPTouch alloc] initWithPoint:NSMakePoint(umpTouchesIterator->touch.x, umpTouchesIterator->touch.y) 
												   pressure:umpTouchesIterator->touch.pressure];
					[mutableTouches addObject:touch];
					[touch release];
				}
				
			}
			
			// update touches array
			[touches release];
			touches = [[NSArray alloc] initWithArray:mutableTouches];
			
			// inform delegate
			[self performSelectorOnMainThread:@selector(didReceiveTouches:) withObject:touches waitUntilDone:YES];
			
		}
		
		// cleanup
		[pool drain];
		
		// sleep for 0.1ms
		[NSThread sleepForTimeInterval:0.0001];
		
	}

	// cleanup
	[mutableTouches release];

	listening = NO;
}

// received touches (called on main thread)
- (void)didReceiveTouches:(NSArray *)someTouches;
{
	[delegate umpListener:self didReceiveTouches:someTouches];
}

// error in listening thread
- (void)didFailWithError:(NSError *)error;
{
	[delegate umpListenerDidFail:error];
}


# pragma mark private methods

// init ftdi & ump
- (BOOL)initFTDI:(NSError **)error;
{
    int f;
	
	// ftdi configuration
	
    // Init ftdi
	f = ftdi_init(&ftdic);
    if(f < 0) {
		NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:@"ftdi_init failed" forKey:NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain:@"UMP" code:f userInfo:errorInfo];
		return NO;
    }
	
    // Select first interface
    ftdi_set_interface(&ftdic, [[properties valueForKey:@"interfaceID"] intValue]);
	
    // Open device
    f = ftdi_usb_open(&ftdic, [[properties valueForKey:@"vendorID"] intValue], [[properties valueForKey:@"productID"] intValue]);
    if (f < 0)
    {
        NSString *errorMessage = [NSString stringWithFormat:@"unable to open ftdi device: %s\n", ftdi_get_error_string(&ftdic)];
		NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain:@"UMP" code:f userInfo:errorInfo];
		return NO;
    }
	
    // Set baudrate
    f = ftdi_set_baudrate(&ftdic, [[properties valueForKey:@"baudrate"] intValue]);
    if (f < 0)
    {
        NSString *errorMessage = [NSString stringWithFormat:@"unable to set baudrate: %s\n", ftdi_get_error_string(&ftdic)];
		NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain:@"UMP" code:f userInfo:errorInfo];
		return NO;
    }
	
    // Set latency timer
    f = ftdi_set_latency_timer(&ftdic, [[properties valueForKey:@"latencyTimer"] intValue]);
    if (f < 0)
    {
        NSString *errorMessage = [NSString stringWithFormat:@"unable to set latency timer: %s\n", ftdi_get_error_string(&ftdic)];
		NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain:@"UMP" code:f userInfo:errorInfo];
		return NO;
    }
	
	// Set read buffer
	f = ftdi_read_data_set_chunksize(&ftdic, [[properties valueForKey:@"bufferSize"] intValue]);
	if(f < 0)
	{
        NSString *errorMessage = [NSString stringWithFormat:@"unable to set chunksize: %s\n", ftdi_get_error_string(&ftdic)];
		NSDictionary *errorInfo = [NSDictionary dictionaryWithObject:errorMessage forKey:NSLocalizedDescriptionKey];
		*error = [NSError errorWithDomain:@"UMP" code:f userInfo:errorInfo];
		return NO;
	}
	
	// Init ump
	ump_init(&umpc, [[properties valueForKey:@"width"] intValue], [[properties valueForKey:@"height"] intValue]);
	
	return YES;
}

// deinit ftdi & ump
- (BOOL)deinitFTDI:(NSError **)error;
{
	ump_deinit(&umpc);
	
    ftdi_usb_close(&ftdic);
    ftdi_deinit(&ftdic);
	
	return YES;
}


@end
